<?php
class products {
  private $legal_state = array( 0=>'Старт', 2=>'Процес', 6=>'Финиш', 14=>'Готово' );
  
  function __construct() {
  }

  public function listprod() {
    $data = new jqGridData();
    try
    {
// Только зарегистрированные пользователи могут видеть данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

		$rwd = $_SESSION['priv']->products->rwd;
		if( !$rwd  ) throw new Exception(ERR_M::DATA_ACC_DISABLED, 99);

// Проверяем данные
        $page    = ( isset($_REQUEST["page"]) && $_REQUEST["page"] > 0 ) ? $_REQUEST["page"] : 1;
        $limit    = ( isset($_REQUEST["rows"]) && $_REQUEST["rows"] > 0 ) ? $_REQUEST["rows"] : 1;
        $sidx    = ( isset($_REQUEST["sidx"]) && $_REQUEST["sidx"] != '' ) ? $_REQUEST["sidx"] : 'prod_start';
        $sord    = ( isset($_REQUEST["sord"]) && strtolower($_REQUEST["sord"]) == 'desc' ) ? 'DESC' : 'ASC';
        $search    = ( isset($_REQUEST["_search"]) && strtolower($_REQUEST["_search"]) == 'true') ? true : false;
 
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Сортировка
		switch( $sidx ) {
			case 'prod_start': $sidx = 'prod.prod_start'; break;
			case 'art_code': $sidx = 'art.art_code'; break;
			case 'prod_finish': $sidx = 'prod.prod_finish'; break;
			case 'status': $sidx = 'status'; break;
		}
		
// Фильтр
        $whc = array();
		$whc[] = 'prod_tostock IS NULL';
		
        if( $search )
        {
            if(isset($_REQUEST["id"]) && ctype_digit($_REQUEST["id"]) ) {
				$whc[] = "prod.prod_id=" . $db->quote($_REQUEST["prod_id"]);
		    }
            
            if(isset($_REQUEST["status"]) && ctype_digit($_REQUEST["status"]) ) {
				$whc[] = "status=" . $db->quote($_REQUEST["status"]);
		    }
            
            if(isset($_REQUEST["prod_start"]) && ( $dt = Tools::date_to_search_str( $_REQUEST["prod_start"] )) != "") {
				$whc[] = "prod.prod_start LIKE " . $db->quote("%".$dt."%");
            }
			
            if(isset($_REQUEST["art_code"]) && $_REQUEST["art_code"] != "") {
				$whc[] = "art.art_code LIKE " . $db->quote("%".$_REQUEST["art_code"]."%");
            }
			
            if(isset($_REQUEST["art_full_name"]) && $_REQUEST["art_full_name"] != "") {
		    	$whc[] = "art.art_full_name LIKE " . $db->quote("%".$_REQUEST["art_full_name"]."%");
            }
			
            if(isset($_REQUEST["prod_finish"]) && ( $dt = Tools::date_to_search_str( $_REQUEST["prod_finish"] )) != "") {
				$whc[] = "prod.prod_finish LIKE " . $db->quote("%".$dt."%");
            }
			
		}

// Фильтр привилегий
		if( $rwd & PERM_BITS::READ_OTH ) $whc[] = '1 = 1';
		elseif( $rwd & PERM_BITS::READ_GRP ) 
			$whc[] = 'prod_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::READ_OWN ) $whc[] = 'prod_user_id = '.$_SESSION['user_id'];
		else $whc[] = '1 = 0';

// Окончательно формируем условие
        $whereclaus = (count($whc) != 0 ) ? " WHERE " . implode(" AND ", $whc) : "";
		unset($whc);
            
// Получаем количество записей с учетом фильтра
        $join1 = " INNER JOIN articles AS art ON art.art_id=prod.prod_art_id ";
		$join2 = " LEFT JOIN ( SELECT p.prod_id,".
			"IF(( SELECT 1 FROM specreal WHERE spr_sto_id>0 AND spr_prod_id=p.prod_id limit 1 ),2,0)+".
			"IF(( SELECT 1 FROM specreal WHERE spr_sto_id=0 AND spr_prod_id=p.prod_id limit 1 ),0,4)+".
			"IF( p.prod_finish IS NULL,0,8) AS status FROM products AS p ) as pstat ".
			"ON pstat.prod_id=prod.prod_id ";
		$join3 = " LEFT JOIN metrics ON met_id = art_met_id ";
			
        $sql = "SELECT COUNT(*) AS count FROM products AS prod ".$join1.$join2.$whereclaus;
        $recset = $db->query( $sql );
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		$count = $row['count'];
		$recset->closeCursor();
            
// Пустая таблица
        if ($count == 0)
			throw new Exception(ERR_M::SUCCESS, 0);
            
// Рассчитаем сколько всего страниц займут данные в БД
    	if( $count > 0 && $limit > 0) {
    		$total_pages = ceil($count/$limit);
    	} else {
    		$total_pages = 0;
    	}                  
 
		if ($page > $total_pages) $page=$total_pages;                  
 
		$start = $limit*$page - $limit;                   
		if($start < 0) $start = 0;                  
 
// Формируем запрос
		$sql = 	"SELECT prod.prod_id,status,prod_start,prod_art_id,art_code,".
						"art_full_name,prod_num,met_name,prod_finish,prod_comment ".
				"FROM products AS prod ".$join1.$join2.$join3.$whereclaus." ORDER BY ".$sidx." ".$sord.
		        " LIMIT ".$start.", ".$limit;

		$recset = $db->query($sql);                  
 
// Формирование объекта для возврата
        
    	$data->page      = $page;
    	$data->total     = $total_pages;
    	$data->records   = $count;
    		
    // Строки данных для таблицы
		$i = 0;
		$currdate = date('Y-m-d');
		while($row = $recset->fetch(PDO::FETCH_ASSOC)) {
			$status = isset( $this->legal_state[$row['status']] ) ? $row['status'] : "0";
			
			$data->rows[$i]['id'] = $row['prod_id'];
			$data->rows[$i]['cell'] = array(
					$row['prod_id'],
					$status,
					Tools::str_iso2local_date( $row['prod_start'] ),
					$row['prod_art_id'],
					$row['art_code'],
					$row['art_full_name'],
					$row['prod_num'],
					$row['met_name'],
					Tools::str_iso2local_date( $row['prod_finish'] ),
					$row['prod_comment']
		    );
		    $i++;
		}
    }
	catch (PDOException $e) {
		$data->userdata->errcode = 1;
		$data->userdata->errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$data->userdata->errcode = $e->getCode();
		$data->userdata->errmess = $e->getMessage();
	}
 
	return $data;
  }
  
  
  public function prodstatus() {
	$data = 0;
	$errcode = 0;
	$errmess = '';
	try
    {
// Только зарегистрированные пользователи могут видеть данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

		$rwd = $_SESSION['priv']->products->rwd;
		if( !$rwd  ) throw new Exception(ERR_M::DATA_ACC_DISABLED, 99);

// Проверяем данные
		$prod_id = isset($_POST['prod_id']) ? trim(strip_tags($_POST['prod_id'])) : '';
        if (!$prod_id || !ctype_digit( $prod_id ))
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);      
 
// Открываем базу данных
		$db = App::me()->dbOpen();	
		$data = $this->getprodstatus( $db, $prod_id );
		if( is_bool( $data ) )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);      
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}
 
	return array('errcode'=>$errcode,'errmess'=>$errmess, 'data'=>$data);
  }
  
  
  public function insprod() {

    $prod_start		= isset($_POST['prod_start']) ? trim(strip_tags($_POST['prod_start'])) : '';
    $prod_art_id	= isset($_POST['prod_art_id']) ? trim(strip_tags($_POST['prod_art_id'])) : '';
    $art_code		= isset($_POST['art_code']) ? trim(strip_tags($_POST['art_code'])) : '';
    $prod_num		= isset($_POST['prod_num']) ? trim(strip_tags($_POST['prod_num'])) : '';
    $prod_comment	= isset($_POST['prod_comment']) ? trim(strip_tags($_POST['prod_comment'])) : '';
	
	$errcode = 0;
	$errmess = '';
	
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$prod_user_id = $spr_user_id = $_SESSION['user_id'];
		
//Есть ли разрешение на вставку записей
		$rwd_prod = $_SESSION['priv']->products->rwd;
		$rwd_spr  = $_SESSION['priv']->specreal->rwd;
		if( !$rwd_prod & PERM_BITS::WRITE_OWN || !$rwd_spr & PERM_BITS::WRITE_OWN ) 
			throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
		$errdata = "";
		
// Открываем базу данных
		$db = App::me()->dbOpen();
		
// prod_start
		if( !$prod_start ) $errdata .= ($errdata?'<br>':'')."- Не указана дата начала производства.";
		elseif( ( $prod_start = Tools::check_date($prod_start) ) === false )
			$errdata .= ($errdata?'<br>':'')."- Дата начала производства указана неправильно.";
			
// art_code
		if( !$art_code ) $errdata .= ($errdata?'<br>':'')."- Не указан шифр изделия.";
		elseif( !$prod_art_id || !ctype_digit( $prod_art_id ) ) 
			$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор изделия.';
		else {
// Считываем данные из справочника
			$sql =	'SELECT art_code, spm_num FROM articles AS art '.
					'LEFT JOIN '.
						'( SELECT a.art_id, IF(( SELECT 1 FROM specmodel WHERE spm_art_id=a.art_id limit 1 ),1,0) AS spm_num '.
						'FROM articles AS a ) as spm '.
					'ON spm.art_id=art.art_id '.
					'WHERE art.art_id = '.$db->quote($prod_art_id);
			$recset = $db->query($sql);                  
			$row = $recset->fetch(PDO::FETCH_ASSOC);
			if( !$row || $row['art_code'] != $art_code) {
				$errdata .= ($errdata?'<br>':'').'- Неправильно указан идентификатор изделия.';
			}
			elseif( !$row['spm_num'] ) {
				$errdata .= ($errdata?'<br>':'').'- Отсутствует типовая спецификация изделия.';
			}
			$recset->closeCursor();
		}
		
// prod_num
		if( !$prod_num )
			$errdata .= ($errdata?'<br>':'').'- Не указано количество изделий.';
		else {
			$prod_num = str_replace(',', '.', $prod_num);
			if( !is_numeric( $prod_num ) || $prod_num <= 0 )
				$errdata .= ($errdata?'<br>':'')."- Неправильно указано количество изделий.";
		}
		
//				
// Если есть ошибки - можно уже закончить
//
		if( $errdata ) throw new Exception($errdata, 1);
		
// Пишем данные
		try { 
			$db->beginTransaction();

			// Добавляем запись изделия
			$sql =	'INSERT INTO products ( prod_start, prod_art_id, prod_num, prod_comment, prod_user_id ) '.
					'VALUES ('.$db->quote($prod_start).", ".$db->quote($prod_art_id).", ".
							$db->quote($prod_num).", ".$db->quote($prod_comment).", ".$db->quote($prod_user_id).")";
							
			$db->exec($sql);
			$prod_id = $db->lastInsertId();

			// Копируем типовую спецификацию
			$sql =	"INSERT INTO specreal ( spr_prod_id, spr_uni_id, spr_amount, spr_user_id ) ".
					"SELECT ${prod_id}, spm_uni_id, spm_amount, ${spr_user_id} FROM specmodel WHERE spm_art_id = ${prod_art_id}";
			$db->exec($sql);
			
			$db->commit();
		} 
		catch(PDOExecption $e) { 
			$db->rollback(); 
			$errcode = 1;
			$errmess = ERR_M::DATABASE_ERROR;
		} 
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

/*
	Правила редактирования изделий.
	1.	После создания записи об изделии его шифр изменяться не может. 
		Поскольку это было бы аналогично удалению прежнего и созданию нового изделия.
	2.	Дата начала изготовления и количество может меняться до первой выписки 
		материалов со склада по этому изделию.
	3.	Дата завершения изготовления может быть проставлена только после выписки со
		склада всех позиций спецификации. 
		И может быть изменена ( не удалена ) только на протяжении текущих суток.
	4.	После завершения текущих суток после ввода даты изготовления любое редактирование
		записи изделия невозможно.
	В соответствии с этими правилами вводим флаги:
	
	status ( 0000 00A0 )	
		A:	0 - стадия изготовления, допускаются изменения, определяемые флагом 'specstate'
			1 - все операции по изготовлению завершены и любое редактирование невозможно.
	specstate ( 0000 0BA0 )
		A:	0 - нет выписки со склада ( нет записей с кодом материала не 0 )
			1 - выписка со склада началась ( есть записи с кодом материала не 0 )
		B:	0 - выписка со склада еще не завершена ( есть записи с кодом материала 0 )
			1 - все позиции спецификации прошли выписку ( нет записей с кодом материала 0 )
*/
  
  public function updprod() {

    $prod_id		= isset($_POST['id']) ? trim(strip_tags($_POST['id'])) : '';
    $prod_start		= isset($_POST['prod_start']) ? trim(strip_tags($_POST['prod_start'])) : '';
    $prod_num		= isset($_POST['prod_num']) ? trim(strip_tags($_POST['prod_num'])) : '';
    $prod_finish	= isset($_POST['prod_finish']) ? trim(strip_tags($_POST['prod_finish'])) : '';
    $prod_comment	= isset($_POST['prod_comment']) ? trim(strip_tags($_POST['prod_comment'])) : '';
	
	$errcode = 0;
	$errmess = '';
	
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$prod_user_id = $_SESSION['user_id'];
		
//Есть ли разрешение на изменение записей
		$rwd = $_SESSION['priv']->products->rwd;
		if( ($rwd & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
		$errdata = "";
		
// prod_id
        if (!$prod_id || !ctype_digit( $prod_id ))
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
        
// Открываем базу данных
		$db = App::me()->dbOpen();
		
// Есть ли разрешение на апдейт этой записи
		if( $rwd & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd & PERM_BITS::WRITE_GRP ) 
			$where = 'prod_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::WRITE_OWN ) $where = 'prod_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND prod_id = '.$db->quote($prod_id);
		
		$sqlreq = 'SELECT prod_start, prod_finish FROM products WHERE '.$where.' LIMIT 1';
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( !$row ) {
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
        }
		$ostart = $row['prod_start'];
		$ofinish = $row['prod_finish'];
		$recset->closeCursor();
		
// Формируем статус и состояние спецификации
		$status = 0;
		$specstate = $this->getprodstatus( $db, $prod_id );
		
// Разные этапы
		$sql = '';
		switch( $specstate & 0x6 ) {
		case 0:		// Еще не было выдачи со склада		
			if( !$prod_start ) $errdata .= ($errdata?'<br>':'')."- Не указана дата начала изготовления.";
			elseif( ( $prod_start = Tools::check_date($prod_start) ) === false )
				$errdata .= ($errdata?'<br>':'')."- Дата начала изготовления указана неправильно.";
			if( $errdata )
				throw new Exception( $errdata, 1);
				
// prod_num
			if( !$prod_num )
				$errdata .= ($errdata?'<br>':'').'- Не указано количество изделий.';
			else {
				$prod_num = str_replace(',', '.', $prod_num);
				if( !is_numeric( $prod_num ) || $prod_num <= 0 )
					$errdata .= ($errdata?'<br>':'')."- Неправильно указано количество изделий.";
			}
		
			$sql =	"UPDATE products SET ".
						"prod_start	  = ".$db->quote($prod_start).", ".
						"prod_num	  = ".$db->quote($prod_num).", ".
						"prod_comment = ".$db->quote($prod_comment)." ".
					"WHERE prod_id = ".$db->quote($prod_id);
			break;
			
		case 2:		// Выдача со склада начата
			$sql =	"UPDATE products SET ".
						"prod_comment = ".$db->quote($prod_comment)." ".
					"WHERE prod_id = ".$db->quote($prod_id);
			break;
		
		case 4:		// Запрещенная комбинация
				break;
				
		case 6:		// Все позиции спецификации выданы со склада		
			if( !$prod_finish ) $errdata .= ($errdata?'<br>':'')."- Не указана дата выпуска изделия.";
			elseif( ( $prod_finish = Tools::check_date($prod_finish) ) === false )
				$errdata .= ($errdata?'<br>':'')."- Дата выпуска изделия указана неправильно.";
			elseif( $prod_finish < $ostart )
				$errdata .= ($errdata?'<br>':'')."- Дата выпуска изделия не может быть меньше даты начала изготовления.";
				
			if( $errdata )
				throw new Exception( $errdata, 1);
				
			$sql =	"UPDATE products SET ".
						"prod_finish  = ".$db->quote($prod_finish).", ".
						"prod_comment = ".$db->quote($prod_comment)." ".
					"WHERE prod_id = ".$db->quote($prod_id);
			break;
		}
		
		if( $sql ) $db->exec($sql);
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

  
  public function delprod() {
    $errmess = "";
	$errcode = 0;
		
    $prod_ids = (isset($_REQUEST['id']) && $_REQUEST['id'] != '') ?
        explode(",", $_REQUEST["id"], 100) : array();


    try
    {
// Только зарегистрированные пользователи могут удалять данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

        if (!Tools::is_numeric_array( $prod_ids ))
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
        
		$prod_id_list = implode(',', $prod_ids);

// Есть ли разрешение на удаление хоть каких либо записей products
		$rwd_prod = $_SESSION['priv']->products->rwd;
		if( ($rwd_prod & PERM_BITS::DELETE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);
		
// Есть ли разрешение на удаление хоть каких либо записей specreal
		$rwd_spr = $_SESSION['priv']->specreal->rwd;
		if( ($rwd_spr & PERM_BITS::DELETE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);
		
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Есть ли разрешение на удаление записей products
		if( $rwd_prod & PERM_BITS::DELETE_OTH ) $where = '1 = 1';
		elseif( $rwd_prod & PERM_BITS::DELETE_GRP ) 
			$where = 'prod_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_prod & PERM_BITS::DELETE_OWN ) $where = 'prod_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND prod_id IN ('.$prod_id_list.')';
		
		$sql =	"( SELECT 1 AS sort_col, COUNT(*) AS count FROM products WHERE ${where} ) UNION ALL ".
				"( SELECT 2, COUNT(*) AS count FROM products WHERE prod_id IN ( ${prod_id_list} ) AND prod_finish IS NOT NULL ) ".
				"ORDER BY sort_col";
		$recset = $db->query( $sql );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( $row['count'] != count($prod_ids) ) {
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
        }    
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( $row['count'] ) {
			throw new Exception('Изделия с установленной датой выпуска удалению не подлежат', 99);
        }    
		$recset->closeCursor();

// Есть ли разрешение на удаление записей specreal
		if( $rwd_spr & PERM_BITS::DELETE_OTH ) $where = '1 = 1';
		elseif( $rwd_spr & PERM_BITS::DELETE_GRP ) 
			$where = 'spr_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_spr & PERM_BITS::DELETE_OWN ) $where = 'spr_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND spr_prod_id IN ('.$prod_id_list.')';
		
// Число записей спец. на которые есть права
		$recset = $db->query( 'SELECT COUNT(*) AS count FROM specreal WHERE '.$where );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		$nacc = $row['count'];
		$recset->closeCursor();

// Общее число записей спец. по этому артиклю
		$sql =	"( SELECT 1 AS sort_col, COUNT(*) AS count FROM specreal WHERE spr_prod_id IN ( ${prod_id_list} ) ) ".
				"UNION ALL ".
				"( SELECT 2, COUNT(*) AS count FROM specreal WHERE spr_prod_id IN ( ${prod_id_list} ) AND spr_sto_id > 0 ) ".
				"ORDER BY sort_col";
		$recset = $db->query( $sql );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		$nall = $row['count'];
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		$nstf = $row['count'];
		$recset->closeCursor();

		if( $nacc != $nall ) {
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
        }    
		
		if( $nstf ) {
			throw new Exception('Сначала верните на склад материалы по которым есть выписка.', 1);
        }    
		
// Проверяем задействована ли эта запись в других таблицах
/*
		$sqlreq =   "SELECT 'Комплектация изделий' as a FROM `incoming` WHERE inc_build_id IN (".$build_id_list.") ".
					"UNION ".
					"SELECT 'Приход по скдаду' as a  FROM `outgoing` WHERE out_build_id IN (".$build_id_list.") ".
					"UNION ".
					"SELECT 'остатки по складу' as a  FROM `parcel` WHERE par_build_id IN (".$build_id_list.") ";
		$recset = $db->query($sqlreq);
		$errdata = '';
		while( $row = $recset->fetch(PDO::FETCH_ASSOC) ) {
			$errdata .= ($errdata?', ':'')."'".$row['a']."'"; 
		}
		if( $errdata != '' )
			throw new Exception( "Удаляемые объекты задействованы в следующих документах:<br>".$errdata, 1);
*/

// Удаляем записи из справочника
		try { 
			$db->beginTransaction();
			$sqlreq = 'DELETE FROM specreal WHERE spr_prod_id IN ('.$prod_id_list.')'.";\n".
					'DELETE FROM products WHERE prod_id IN ('.$prod_id_list.')'.";";
			$db->exec($sqlreq);

			$db->commit();
		} 
		catch(PDOExecption $e) { 
			$db->rollback(); 
			$errcode = 1;
			$errmess = ERR_M::DATABASE_ERROR;
		} 
	}
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }


  public function prodstock() {
    $errmess = "";
	$errcode = 0;
	$errdata = array();	
		
    $prod_ids = (isset($_REQUEST['prod_ids']) && $_REQUEST['prod_ids'] != '') ?
        explode(",", $_REQUEST["prod_ids"], 100) : array();

    try
    {
// Только зарегистрированные пользователи могут удалять данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

        if (!Tools::is_numeric_array( $prod_ids ))
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
        
		$prod_id_list = implode(',', $prod_ids);

// Есть ли разрешение на изменение хоть каких либо записей products
		$rwd_prod = $_SESSION['priv']->products->rwd;
		$rwd_prs  = $_SESSION['priv']->prodstock->rwd;
		
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Есть ли разрешение на изменение записей в products
		if( $rwd_prod & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_prod & PERM_BITS::WRITE_GRP ) 
			$where = 'prod_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_prod & PERM_BITS::WRITE_OWN ) $where = 'prod_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND prod_id IN ('.$prod_id_list.')';
		
		$rs = $db->query( "SELECT COUNT(*) AS count FROM products WHERE ${where}" );		
		$row = $rs->fetchObject();
		if( $row->count != count($prod_ids) )
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
		$rs->closeCursor();

// Проверим, все ли изделия из списка изготовлены.
// Заодно составим массив артиклей
		$art_ids = array();
		$sql =  "SELECT prod_art_id FROM products WHERE prod_id IN ( ${prod_id_list} ) AND prod_finish IS NOT NULL";
		$rs = $db->query( $sql );
		while( $row = $rs->fetchObject() ) {
			$art_ids[] = $row->prod_art_id;
		}
		$rs->closeCursor();
		
		if( count( $art_ids ) != count( $prod_ids ) )
			throw new Exception('Не все изделия из списка прошли выпуск.<br>Передаче на склад отменена.', 1);
    
		$art_id_list = implode(',', $art_ids);
		
// Есть ли разрешение на изменение записей в prodstock
		if( $rwd_prs & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_prs & PERM_BITS::WRITE_GRP ) 
			$where = 'prs_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_prs & PERM_BITS::WRITE_OWN ) $where = 'prs_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= " AND prs_art_id IN ( ${art_id_list} )";
		
		$sql =	"SELECT COUNT(*) AS count FROM prodstock WHERE ${where}";
		$rs = $db->query( $sql );		
		$row = $rs->fetchObject();
		if( $row->count != count($art_ids) )
			throw new Exception("Либо на складе заведены остатки не по всем передаваемым изделиям, ".
								"либо не на все остатки у Вас есть права на изменение.", 1);
		$rs->closeCursor();

// Компилим SQL
		$upd_prod =	$db->prepare('UPDATE products SET prod_tostock = CURDATE() WHERE prod_id = :prod_id');
		$upd_prs  =	$db->prepare('UPDATE prodstock SET prs_amount = prs_amount + :prod_num WHERE prs_art_id = :art_id');

// Отправляем изделия на склад
		$rs = $db->query("SELECT prod_id, prod_art_id, prod_num FROM products WHERE prod_id IN ( ${prod_id_list} )");
		while( $row = $rs->fetchObject() ) {
			try { 
				$db->beginTransaction();
				$upd_prs->execute( array( ':prod_num'=>$row->prod_num, ':art_id'=>$row->prod_art_id ) );
				$upd_prod->execute( array( ':prod_id'=>$row->prod_id ) );
				$db->commit();
			} 
			catch(PDOExecption $e) { 
				$db->rollback(); 
				$errdata[] = $row->prod_id;
			} 
		}
	}
	catch (PDOException $e) {
		$errcode = 1;
//		$errmess = ERR_M::DATABASE_ERROR;
		$errmess = $e->getMessage();
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	if( !$errcode && count( $errdata ) ) {
		$errcode = 1;
		$errmess = "Не все изделия успешно отправлены на склад. Проверьте список.";
	}
	
	return array('errcode'=>$errcode,'errmess'=>$errmess, 'data'=>$errdata);
  }


  public function selstockone() {
  
    $data = new jqGridData();
    try
    {
// Только зарегистрированные пользователи могут видеть данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

		$rwd = $_SESSION['priv']->stock->rwd;
		if( !$rwd  ) throw new Exception(ERR_M::DATA_ACC_DISABLED, 99);

// Проверяем данные
        $page    = ( isset($_REQUEST["page"]) && $_REQUEST["page"] > 0 ) ? $_REQUEST["page"] : 1;
        $limit    = ( isset($_REQUEST["rows"]) && $_REQUEST["rows"] > 0 ) ? $_REQUEST["rows"] : 1;
        $sidx    = ( isset($_REQUEST["sidx"]) && $_REQUEST["sidx"] != '' ) ? $_REQUEST["sidx"] : 'sto_id';
        $sord    = ( isset($_REQUEST["sord"]) && strtolower($_REQUEST["sord"]) == 'desc' ) ? 'DESC' : 'ASC';
        $search    = ( isset($_REQUEST["_search"]) && strtolower($_REQUEST["_search"]) == 'true') ? true : false;
 
        $spr_id = isset($_REQUEST["spr_id"]) ? trim(strip_tags($_REQUEST["spr_id"])) : '';
        if ( !$spr_id || !ctype_digit($spr_id) )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
   
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Определяем идентификатор материала и нужное количество
		$sql =	"SELECT spr_uni_id, spr_amount, prod_num FROM specreal ".
				"INNER JOIN products ON prod_id = spr_prod_id ".
				"WHERE spr_id = ".$db->quote( $spr_id );
		$rs = $db->query( $sql );
		$row = $rs->fetchObject();
		if( !$row )	throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
		$uni_id = $row->spr_uni_id;
		$uni_amount = $row->spr_amount * $row->prod_num;
		$rs->closeCursor();
		
// Сортировка
		switch( $sidx ) {
			case 'sto_id': $sidx = 'sto_id'; break;
			case 'sto_remain': $sidx = 'sto_remain'; break;
		}
		
// Фильтр
        $whc = array();
		$whc[] = "sto_uni_id=" . $db->quote($uni_id);

// Фильтр привилегий
		if( $rwd & PERM_BITS::READ_OTH ) $whc[] = '1 = 1';
		elseif( $rwd & PERM_BITS::READ_GRP ) 
			$whc[] = 'sto_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::READ_OWN ) $whc[] = 'sto_user_id = '.$_SESSION['user_id'];
		else $whc[] = '1 = 0';

// Отсекаем списанные записи
		$whc[] = 'sto_retire_date IS NULL';
		
// Окончательно формируем условие
        $whereclaus = (count($whc) != 0 ) ? " WHERE " . implode(" AND ", $whc) : "";
		unset($whc);
            
// Получаем количество записей с учетом фильтра
        $sqlreq = "SELECT COUNT(*) AS count FROM stock AS sto ".$whereclaus;
        $recset = $db->query( $sqlreq );
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		$count = $row['count'];
		$recset->closeCursor();
            
// Пустая таблица
        if ($count == 0)
			throw new Exception(ERR_M::SUCCESS, 0);

// Получаем данные на unit
        $uni_joins = 
                " INNER JOIN metrics AS met ON met.met_id = units.uni_met_id ".
                " LEFT JOIN metrics AS opt ON opt.met_id = units.uni_met_opt_id ";

		$sql = 	"SELECT uni_name, uni_full, uni_met_id, met.met_name AS met_name, ".
					"IFNULL(uni_met_opt_id,'') AS uni_met_opt_id, ".
					"IFNULL(opt.met_name,'') AS met_opt_name, ".
					"IFNULL(uni_met_mul,'') AS uni_met_mul ".
				"FROM units ".$uni_joins." WHERE uni_id=".$db->quote($uni_id)." LIMIT 1";

		$rs = $db->query($sql);                  
		$row = $rs->fetch(PDO::FETCH_ASSOC);
		if( !$row )
			throw new Exception("Материал в справочнике не найден", 1);

		$uni_name = $row['uni_name'];
		$uni_full = $row['uni_full'];
		$uni_met_id = $row['uni_met_id'];
		$met_name = $row['met_name'];
		$uni_met_opt_id = $row['uni_met_opt_id'];
		$met_opt_name = $row['met_opt_name'];
		$uni_met_mul = $row['uni_met_mul'];
		$rs->closeCursor();
				
// Рассчитаем сколько всего страниц займут данные в БД
    	if( $count > 0 && $limit > 0) {
    		$total_pages = ceil($count/$limit);
    	} else {
    		$total_pages = 0;
    	}                  
 
		if ($page > $total_pages) $page=$total_pages;                  
 
// Формируем запрос
		$sqlreq = 	'SELECT  sto_id, sto_uni_id, sto_number, sto_remain, sto_metric, sto_price, sto_comment '.
					'FROM stock '.$whereclaus." ORDER BY ".$sidx." ".$sord.
		            " LIMIT ".$limit;

		$recset = $db->query($sqlreq);                  
 
// Формирование объекта для возврата
        
    	$data->page      = $page;
    	$data->total     = $total_pages;
    	$data->records   = $count;
    		
    // Строки данных для таблицы
		$i = 0;
		while($row = $recset->fetch(PDO::FETCH_ASSOC)) {
			
			$data->rows[$i]['id'] = $row['sto_id'];
/*
			$data->rows[$i]['cell'] = array(
					$row['sto_id'],
					$row['sto_uni_id'],
					$uni_name,
					$uni_full,
					$row['sto_number'],
					$row['sto_remain'],
				    $row['sto_metric'],
				    ($row['sto_metric'] == $uni_met_id) ? $met_name : 
						(($row['sto_metric'] == $uni_met_opt_id) ? $met_opt_name : ''),
				    $row['sto_price'],
				    $row['sto_comment']
		    );
*/
			$m_sto_number = ($row['sto_metric'] == $uni_met_id) ? $row['sto_number'] :
							(($row['sto_metric'] == $uni_met_opt_id) ? $row['sto_number'] * $uni_met_mul : '');							
			$m_sto_remain =	($row['sto_metric'] == $uni_met_id) ? $row['sto_remain'] :
							(($row['sto_metric'] == $uni_met_opt_id) ? $row['sto_remain'] * $uni_met_mul : '');							
			$m_sto_price =	($row['sto_metric'] == $uni_met_id) ? $row['sto_price'] :
							(($row['sto_metric'] == $uni_met_opt_id) ? $row['sto_price'] / $uni_met_mul : '');							

			$status = 0;
			if( $m_sto_remain < $uni_amount ) $status |= 0x1;
			$data->rows[$i]['cell'] = array(
					$row['sto_id'],
					$status,
					$uni_name,
					$uni_full,
					$m_sto_number,
					$m_sto_remain,
				    $met_name,
					$m_sto_price,							
				    $row['sto_comment']
		    );
		    $i++;
		}
    }
	catch (PDOException $e) {
		$data->userdata->errcode = 1;
		$data->userdata->errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$data->userdata->errcode = $e->getCode();
		$data->userdata->errmess = $e->getMessage();
	}
 
	return $data;
  }
  

  public function getstockone() {

    $sto_id		= isset($_POST['sto_id']) ? trim(strip_tags($_POST['sto_id'])) : '';
    $spr_id		= isset($_POST['spr_id']) ? trim(strip_tags($_POST['spr_id'])) : '';
	
	$errcode = 0;
	$errmess = '';
	$eps = 1E-10;
	
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$user_id = $_SESSION['user_id'];
		
//Есть ли разрешение на изменение записей
		$rwd_spr = $_SESSION['priv']->specreal->rwd;
		if( ($rwd_spr & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

		$rwd_sto = $_SESSION['priv']->stock->rwd;
		if( ($rwd_sto & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
		$errdata = "";
		
// sto_id, spr_id, spr_amount
        if (!$sto_id || !ctype_digit( $sto_id ) || !$spr_id || !ctype_digit( $spr_id ) )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);

// Открываем базу данных
		$db = App::me()->dbOpen();
		
// Есть ли разрешение на апдейт записей в specreal
		if( $rwd_spr & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_spr & PERM_BITS::WRITE_GRP ) 
			$where = 'spr_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_spr & PERM_BITS::WRITE_OWN ) $where = 'spr_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND spr_id = '.$db->quote($spr_id);
		
		$sql =	"SELECT spr_uni_id, spr_amount, spr_sto_id, prod_num FROM specreal ".
				"INNER JOIN products ON prod_id = spr_prod_id WHERE ${where}";
		$rs = $db->query( $sql );		
		$row = $rs->fetch(PDO::FETCH_ASSOC);
		if( !$row )	throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);

		$spr_uni_id = $row['spr_uni_id'];
		$need_amount = $row['spr_amount'] * $row['prod_num'];
		$spr_sto_id = $row['spr_sto_id'];
		$rs->closeCursor();

		if( $spr_sto_id ) throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
		
// Есть ли разрешение на апдейт записей в stock
		if( $rwd_sto & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_sto & PERM_BITS::WRITE_GRP ) 
			$where = 'sto_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_sto & PERM_BITS::WRITE_OWN ) $where = 'sto_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND sto_id = '.$db->quote($sto_id);
		
		$sql =	'SELECT sto_uni_id FROM stock WHERE '.$where;
		$rs = $db->query( $sql );		
		$row = $rs->fetch(PDO::FETCH_ASSOC);
		if( !$row ) throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);

		$sto_uni_id = $row['sto_uni_id'];
		$rs->closeCursor();

		if( $spr_uni_id != $sto_uni_id ) throw new Exception(ERR_M::PROTOCOL_ERROR, 1);

// Получаем данные на unit
		$sql = 	"SELECT uni_met_id, IFNULL(uni_met_opt_id,'') AS uni_met_opt_id, ".
						"IFNULL(uni_met_mul,'') AS uni_met_mul ".
				"FROM units WHERE uni_id=".$db->quote($spr_uni_id)." LIMIT 1";

		$rs = $db->query($sql);                  
		$row = $rs->fetch(PDO::FETCH_ASSOC);
		if( !$row )	throw new Exception("Материал в справочнике не найден", 1);

		$uni_met_id = $row['uni_met_id'];
		$uni_met_opt_id = $row['uni_met_opt_id'];
		$uni_met_mul = $row['uni_met_mul'];
		$rs->closeCursor();
				
		try {
			$db->beginTransaction();
		
			$sql =	'SELECT sto_uni_id, sto_remain, sto_metric, sto_retire_date FROM stock '.
					'WHERE sto_id = '.$db->quote($sto_id).' FOR UPDATE';
			$rs = $db->query( $sql );		
			$row = $rs->fetch(PDO::FETCH_ASSOC);
			if( !$row ) throw new Exception('Указанная позиция на складе не найдена', 1);

			$sto_uni_id = $row['sto_uni_id'];
			$sto_remain = $row['sto_remain'];
			$sto_metric = $row['sto_metric'];
			$sto_retire_date = $row['sto_retire_date'];
			$rs->closeCursor();
			
// Делаем еще раз проверку
			if( $spr_uni_id != $sto_uni_id ) throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
			if( !is_null($sto_retire_date) ) throw new Exception('Указанная позиция на складе уже списана', 1);
			
// Приводим метрику специф. к метрике склада. !!! Метрика спецификации всегда основная
			if( $uni_met_opt_id && abs($uni_met_mul) > $eps ) {
				$get_amount = ($sto_metric == $uni_met_opt_id) ? $need_amount / $uni_met_mul :
								( ($sto_metric == $uni_met_id) ? $need_amount : 0 );
			}
			else {
				$get_amount = ($sto_metric == $uni_met_id) ? $need_amount : 0;
			}
			if( abs($get_amount) < $eps ) 
				throw new Exception('Ошибка приведения метрик. Обратитесь к администратору системы', 1); 

// Еще раз проверяем количество
			$sto_remain = $sto_remain - $get_amount;
			if( $sto_remain < 0 && abs($sto_remain) > $eps ) 
				throw new Exception('На складе нет достаточного количества для проведения выписки', 1); 

// Делаем списание
			$curr_date = date('Y-m-d');
			$sql =	"UPDATE stock SET sto_remain = ${sto_remain} WHERE sto_id = ${sto_id};\n".
					"UPDATE specreal SET spr_sto_id = ${sto_id}, spr_sto_date = '${curr_date}' WHERE spr_id = ${spr_id};";
			$db->exec($sql);
			$db->commit();
		}
		catch (PDOException $e) {
			$db->rollback(); 
			$errcode = 1;
			$errmess = ERR_M::DATABASE_ERROR;
		}
		catch ( Exception $e ) {
			$db->rollback(); 
			$errcode = $e->getCode();
			$errmess = $e->getMessage();
		}
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

    
  public function retstocklist() {

    $spr_ids	= isset($_POST['spr_ids']) ? explode(",", trim(strip_tags($_POST['spr_ids']))) : array();
	
	$errcode = 0;
	$errmess = '';
	$eps = 1E-10;
	
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$user_id = $_SESSION['user_id'];
		
//Есть ли разрешение на изменение записей
		$rwd_spr = $_SESSION['priv']->specreal->rwd;
		if( ($rwd_spr & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

		$rwd_sto = $_SESSION['priv']->stock->rwd;
		if( ($rwd_sto & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
        if ( !Tools::is_numeric_array( $spr_ids ))	throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
		$spr_id_list = implode(',', $spr_ids);		

// Открываем базу данных
		$db = App::me()->dbOpen();
		
// Есть ли разрешение на апдейт записей в specreal
		if( $rwd_spr & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_spr & PERM_BITS::WRITE_GRP ) 
			$where = 'spr_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_spr & PERM_BITS::WRITE_OWN ) $where = 'spr_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND spr_id IN ('.$spr_id_list.')';
		
		$sql =	'SELECT spr_sto_id FROM specreal WHERE '.$where;
		$rs = $db->query( $sql );
		$sto_ids = array();
		$spr_count = 0;
		while( $row = $rs->fetch(PDO::FETCH_ASSOC) ) {
			$spr_count++;
			if( !$row['spr_sto_id'] ) continue;
			$sto_ids[] = $row['spr_sto_id'];
		}
		if( $spr_count != count($spr_ids) )	
			throw new Exception("Недостаточно прав на одну или несколько записей в таблице спецификаций.", 99);
		$rs->closeCursor();
		
// Есть ли разрешение на апдейт записей в stock
		$sto_id_list = implode(',', $sto_ids);
		if( $rwd_sto & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_sto & PERM_BITS::WRITE_GRP ) 
			$where = 'sto_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_sto & PERM_BITS::WRITE_OWN ) $where = 'sto_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND sto_id IN ('.$sto_id_list.')';
		
		$sql =	'SELECT COUNT(*) AS count FROM stock WHERE '.$where;
		$rs = $db->query( $sql );		
		$row = $rs->fetch(PDO::FETCH_ASSOC);
		if( $row['count'] != count($sto_ids) ) 
			throw new Exception("Недостаточно прав на одну или несколько записей в таблице склада.", 99);
		$rs->closeCursor();

		// Компилим SQL
		$sel_sto =	$db->prepare('SELECT sto_remain, sto_metric FROM stock WHERE sto_id = :sto_id FOR UPDATE');
		$upd_sto = 	$db->prepare('UPDATE stock SET sto_remain = :sto_remain, sto_retire_date = NULL WHERE sto_id = :sto_id');
		$upd_spr = 	$db->prepare('UPDATE specreal SET spr_sto_id = 0, spr_sto_date = NULL WHERE spr_id = :spr_id');
		

		// Начинаем цикл возврата. Записи, для которых возврат выполнить невозможно - пропускаем
		// но формируем предупреждение

		$errdata = '';
		$sql =	"SELECT spr_id, spr_uni_id, spr_amount, spr_sto_id, prod_num, ".
						"uni_met_id, uni_met_opt_id, uni_met_mul ".
				"FROM specreal AS spr ".
				"INNER JOIN products ON prod_id = spr_prod_id ".
				"INNER JOIN units AS uni ON uni.uni_id = spr.spr_uni_id ".
				"WHERE spr_id IN (".$spr_id_list.")";
		$rs_spr = $db->query( $sql );		
		while( $spr = $rs_spr->fetch(PDO::FETCH_ASSOC) ) {
			$spr_sto_id = $spr['spr_sto_id'];
			if( !$spr_sto_id ) continue;
			$spr_id = $spr['spr_id'];
			$spr_uni_id = $spr['spr_uni_id'];
			$ret_amount = $spr['spr_amount'] * $spr['prod_num'];
			$uni_met_id = $spr['uni_met_id'];
			$uni_met_opt_id = $spr['uni_met_opt_id'];
			$uni_met_mul = $spr['uni_met_mul'];
				
			try {
				$db->beginTransaction();
				$sel_sto->execute( array( ':sto_id'=>$spr_sto_id ) );
				$sto = $sel_sto->fetch(PDO::FETCH_ASSOC);
				if( !$sto ) {
					$errdata .= ($errdata?'<br>':'')."Позиция '${spr_sto_id}' на складе не найдена.";
					$sel_sto->closeCursor();
					$db->commit();
					continue;
				}

				$sto_remain = $sto['sto_remain'];
				$sto_metric = $sto['sto_metric'];
				$sel_sto->closeCursor();
			
				// Приводим метрику специф. к метрике склада. !!! 
				// Метрика спецификации всегда основная
				if( !is_null($uni_met_opt_id) && !is_null($uni_met_mul) ) {
					$ret_amount =	($sto_metric == $uni_met_opt_id) ? $ret_amount / $uni_met_mul :
									( ($sto_metric == $uni_met_id) ? $ret_amount : 0 );
				}
				else $ret_amount = ($sto_metric == $uni_met_id) ? $ret_amount : 0;
			
				if( abs($ret_amount) < $eps ) { 
					$errdata .= ($errdata?'<br>':'')."Ошибка приведения метрик для позиции ".
								"'${spr_sto_id}' склада. Обратитесь к администратору системы";
					$db->commit();
					continue;
				}

				// Делаем возврат
				$sto_remain = $sto_remain + $ret_amount;
				$upd_sto->execute( array( ':sto_id'=>$spr_sto_id, ':sto_remain'=>$sto_remain ) );
				$upd_spr->execute( array( ':spr_id'=>$spr_id ) );

				$db->commit();
			}
			catch (PDOException $e) {
				$db->rollback(); 
				$errcode = 1;
				$errmess = ERR_M::DATABASE_ERROR;
				break;
			}		
		}
		if( $errdata && !$errcode ) $errcode = 1;
		$errmess = $errdata.(($errdata && $errmess)?'<br>':'').$errmess;
		$rs_spr->closeCursor();
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

  
  public function getstocklist() {

    $spr_ids	= isset($_POST['spr_ids']) ? explode(",", trim(strip_tags($_POST['spr_ids']))) : array();
	
	$errcode = 0;
	$errmess = '';
	$eps = 1E-10;
	
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$user_id = $_SESSION['user_id'];
		
//Есть ли разрешение на изменение записей
		$rwd_spr = $_SESSION['priv']->specreal->rwd;
		if( ($rwd_spr & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

		$rwd_sto = $_SESSION['priv']->stock->rwd;
		if( ($rwd_sto & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
        if ( !Tools::is_numeric_array( $spr_ids ))	throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
		$spr_id_list = implode(',', $spr_ids);		

// Открываем базу данных
		$db = App::me()->dbOpen();
		
// Есть ли разрешение на апдейт записей в specreal
		if( $rwd_spr & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_spr & PERM_BITS::WRITE_GRP ) 
			$where = 'spr_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_spr & PERM_BITS::WRITE_OWN ) $where = 'spr_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND spr_id IN ('.$spr_id_list.')';
		
		$sql =	'SELECT COUNT(*) AS count FROM specreal WHERE '.$where;
		$rs = $db->query( $sql );
		$spr = $rs->fetchObject();
		if( $spr->count != count($spr_ids) )	
			throw new Exception("Недостаточно прав на одну или несколько записей в таблице спецификаций.", 99);
		$rs->closeCursor();
		
		// Компилим SQL
		
		// Есть ли разрешение на апдейт записей в stock
		if( $rwd_sto & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd_sto & PERM_BITS::WRITE_GRP ) 
			$where = 'sto_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd_sto & PERM_BITS::WRITE_OWN ) $where = 'sto_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND sto_id = :sto_id';
		
		$acl_sto =  $db->prepare('SELECT COUNT(*) AS count FROM stock WHERE '.$where);
		$sel_sto =	$db->prepare('SELECT sto_id, sto_remain, sto_metric FROM stock '.
								'WHERE sto_uni_id = :sto_uni_id AND sto_retire_date IS NULL ORDER BY sto_id FOR UPDATE');
		$upd_sto = 	$db->prepare('UPDATE stock SET sto_remain = :sto_remain WHERE sto_id = :sto_id');
		$upd_spr = 	$db->prepare('UPDATE specreal SET spr_sto_id = :spr_sto_id, spr_sto_date = :spr_sto_date WHERE spr_id = :spr_id');
		

		// Начинаем цикл выписки.
		
		$errdata = '';
		$sql =	"SELECT spr_id, spr_uni_id, spr_amount, spr_sto_id, prod_num, ".
						"uni_met_id, uni_met_opt_id, uni_met_mul ".
				"FROM specreal AS spr ".
				"INNER JOIN products ON prod_id = spr_prod_id ".
				"INNER JOIN units AS uni ON uni.uni_id = spr.spr_uni_id ".
				"WHERE spr_id IN (".$spr_id_list.")";
		$rs_spr = $db->query( $sql );		
		while( $spr = $rs_spr->fetchObject() ) {
			if( $spr->spr_sto_id ) continue;
				
			$need_amount = $spr->spr_amount * $spr->prod_num;
			
			try {
				$db->beginTransaction();
				$sel_sto->execute( array( ':sto_uni_id'=>$spr->spr_uni_id ) );
				$success = false;
				while($sto = $sel_sto->fetchObject()) {
			
					// Приводим метрику специф. к метрике склада. !!! 
					// Метрика спецификации всегда основная
					if( !is_null($spr->uni_met_opt_id) && !is_null($spr->uni_met_mul) ) {
						$get_amount = ($sto->sto_metric == $spr->uni_met_opt_id) ? $need_amount / $spr->uni_met_mul :
									 (($sto->sto_metric == $spr->uni_met_id) ? $need_amount : 0 );
					}
					else $get_amount = ($sto->sto_metric == $spr->uni_met_id) ? $need_amount : 0;
			
					if( abs($get_amount) < $eps ) { 
						$errdata .= ($errdata?'<br>':'')."Ошибка приведения метрик для позиции '".
								$sto->sto_id."' склада. Обратитесь к администратору системы";
						continue;
					}

					if( $get_amount > $sto->sto_remain ) continue;
					
					// Проверка на разрешение доступа
					$acl_sto->execute( array( ':sto_id'=>$sto->sto_id ) );
					$acl_count = $acl_sto->fetchObject()->count;
					$acl_sto->closeCursor();
					if( !$acl_count ) continue;
					
					$success = true;
					break;
				}
				if( $success ) {
					// Делаем выписку
					$upd_sto->execute( array( ':sto_id'=>$sto->sto_id, ':sto_remain'=>($sto->sto_remain - $get_amount) ) );
					$upd_spr->execute( array( ':spr_id'=>$spr->spr_id, ':spr_sto_id'=>$sto->sto_id, ':spr_sto_date'=>date('Y-m-d') ) );
				}
				else $errdata .= ($errdata?'<br>':'')."Позиция '".$spr->spr_id."' на складе не найдена.";

				$sel_sto->closeCursor();
				$db->commit();
			}
			catch (PDOException $e) {
				$db->rollback(); 
				$errcode = 1;
				$errmess = ERR_M::DATABASE_ERROR;
				break;
			}		
		}
		$rs_spr->closeCursor();

		if( $errdata && !$errcode ) $errcode = 1;
		$errmess = $errdata.(($errdata && $errmess)?'<br>':'').$errmess;
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

        
  public function listuni() {
  
    $data = new jqGridData();
    try
    {
// Только зарегистрированные пользователи могут видеть данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

		$rwd = $_SESSION['priv']->specreal->rwd;
		if( !$rwd  ) throw new Exception(ERR_M::DATA_ACC_DISABLED, 99);

// Проверяем данные
        $prod_id = isset($_REQUEST["prod_id"]) ? $_REQUEST["prod_id"] : '';
		if( $prod_id == '' || !ctype_digit($prod_id) )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);

        $page    = ( isset($_REQUEST["page"]) && $_REQUEST["page"] > 0 ) ? $_REQUEST["page"] : 1;
        $limit    = ( isset($_REQUEST["rows"]) && $_REQUEST["rows"] > 0 ) ? $_REQUEST["rows"] : 1;
        $sidx    = ( isset($_REQUEST["sidx"]) && $_REQUEST["sidx"] != '' ) ? $_REQUEST["sidx"] : 'uni_name';
        $sord    = ( isset($_REQUEST["sord"]) && strtolower($_REQUEST["sord"]) == 'desc' ) ? 'DESC' : 'ASC';
        $search    = ( isset($_REQUEST["_search"]) && strtolower($_REQUEST["_search"]) == 'true') ? true : false;
 
// Открываем базу данных
		$db = App::me()->dbOpen();	

		switch( $sidx ) {
			case 'uni_name': $sidx = 'uni.uni_name'; break;
			case 'spr_sto_id': $sidx = 'spr.spr_sto_id'; break;
			case 'spr_sto_date': $sidx = 'spr.spr_sto_date'; break;
		}
		
// Фильтр
        $whc = array();
		$whc[] = 'spr_prod_id = '.$db->quote($prod_id);
		
		$unilink = false;
        if( $search )
        {
            if(isset($_REQUEST["uni_name"]) && $_REQUEST["uni_name"] != "") {
		    	$whc[] = "uni.uni_name LIKE " . $db->quote("%".$_REQUEST["uni_name"]."%");
				$unilink = true;
            }
			
            if(isset($_REQUEST["uni_full"]) && $_REQUEST["uni_full"] != "") {
		    	$whc[] = "uni.uni_full LIKE " . $db->quote("%".$_REQUEST["uni_full"]."%");
				$unilink = true;
            }
			
            if(isset($_REQUEST["spr_sto_id"]) && $_REQUEST["spr_sto_id"] != "") {
		    	$whc[] = "spr.spr_sto_id = " . $db->quote($_REQUEST["spr_sto_id"]);
            }

            if(isset($_REQUEST["spr_sto_date"]) && ( $dt = Tools::date_to_search_str( $_REQUEST["spr_sto_date"] )) != "") {
				$whc[] = "spr.spr_sto_date LIKE " . $db->quote("%".$dt."%");
            }
			
		}

// Фильтр привилегий
		if( $rwd & PERM_BITS::READ_OTH ) $whc[] = '1 = 1';
		elseif( $rwd & PERM_BITS::READ_GRP ) 
			$whc[] = 'spr_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::READ_OWN ) $whc[] = 'spr_user_id = '.$_SESSION['user_id'];
		else $whc[] = '1 = 0';
		
        $whereclaus = (count($whc) != 0 ) ? " WHERE " . implode(" AND ", $whc) : "";
		unset($whc);
            
// Получаем количество записей с учетом фильтра
         
        $join1 = " INNER JOIN units AS uni ON uni.uni_id = spr.spr_uni_id ";
        $join2 = " INNER JOIN metrics AS met ON met.met_id = uni.uni_met_id ";
        $join3 = " LEFT  JOIN stock AS sto ON sto.sto_id = spr.spr_sto_id ";
            
        $sqlreq = "SELECT COUNT(*) AS count FROM specreal AS spr ".($unilink ? $join1 : '').$whereclaus;
        $recset = $db->query( $sqlreq );
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		$count = $row['count'];
		$recset->closeCursor();
            
// Пустая таблица
        if ($count == 0)
			throw new Exception(ERR_M::SUCCESS, 0);
            
// Рассчитаем сколько всего страниц займут данные в БД
    	if( $count > 0 && $limit > 0) {
    		$total_pages = ceil($count/$limit);
    	} else {
    		$total_pages = 0;
    	}                  
 
		if ($page > $total_pages) $page=$total_pages;                  
 
		$start = $limit*$page - $limit;                   
		if($start < 0) $start = 0;                  
 
// Формируем запрос
		$sqlreq = 	"SELECT spr_id, spr_uni_id, uni_name, uni_full, uni_met_opt_id, uni_met_mul, ".
							"spr_amount, met_name, spr_sto_date, sto_id, sto_metric, sto_price ".
					"FROM specreal AS spr ".$join1.$join2.$join3.$whereclaus." ORDER BY ".$sidx." ".$sord.
		            " LIMIT ".$start.", ".$limit;

		$recset = $db->query($sqlreq);                  
 
// Формирование объекта для возврата
        
    	$data->page      = $page;
    	$data->total     = $total_pages;
    	$data->records   = $count;
    		
    // Строки данных для таблицы
		$i = 0;
		while($row = $recset->fetch(PDO::FETCH_ASSOC)) {
			if( is_null($row['sto_id']) ) {	$sto_id = ''; $sto_price = ''; }
			else {
				$sto_id = $row['sto_id']; 
				
				$sto_metric = $row['sto_metric'];
				$sto_price = $row['sto_price'];
				$uni_met_opt_id = $row['uni_met_opt_id'];
				$uni_met_mul = $row['uni_met_mul'];
				if( $uni_met_opt_id && $sto_metric == $uni_met_opt_id )
					$sto_price = $sto_price / $uni_met_mul;
			}
			$status = 0;
			$data->rows[$i]['id'] = $row['spr_id'];
			$data->rows[$i]['cell'] = array(
					$status,
					$row['spr_id'],
					$row['spr_uni_id'],
					$row['uni_name'],
					$row['uni_full'],
				    $row['spr_amount'],
				    $row['met_name'],
					Tools::str_iso2local_date( $row['spr_sto_date'] ),
				    $sto_id,
				    $sto_price,
		    );
		    $i++;
		}
    }
	catch (PDOException $e) {
		$data->userdata->errcode = 1;
		$data->userdata->errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$data->userdata->errcode = $e->getCode();
		$data->userdata->errmess = $e->getMessage();
	}
 
	return $data;
  }
  
  
  public function insuni() {

    $prod_id		= isset($_POST['prod_id']) ? trim(strip_tags($_POST['prod_id'])) : '';
    $spr_uni_id		= isset($_POST['spr_uni_id']) ? trim(strip_tags($_POST['spr_uni_id'])) : '';
    $uni_name		= isset($_POST['uni_name']) ? trim(strip_tags($_POST['uni_name'])) : '';
    $spr_amount		= isset($_POST['spr_amount']) ? trim(strip_tags($_POST['spr_amount'])) : '';
	
	$errcode = 0;
	$errmess = '';
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$spr_user_id = $_SESSION['user_id'];
		
// идентификатор изделия
		if( $prod_id == '' || !ctype_digit($prod_id) )
					throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
			
//Есть ли разрешение на вставку записей
		$rwd = $_SESSION['priv']->specreal->rwd;
		if( ($rwd & PERM_BITS::WRITE_OWN) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
		$errdata = "";
		
// Открываем базу данных
		$db = App::me()->dbOpen();	

// uni_name			
		if( !$uni_name ) $errdata .= ($errdata?'<br>':'').'- Не указан шифр материала.';
		elseif( !$spr_uni_id || !ctype_digit( $spr_uni_id ) ) 
			$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор материала.';
		else {
// Считываем данные из справочника
			$recset = $db->query('SELECT uni_name FROM units WHERE uni_id = '.$db->quote($spr_uni_id));                  
			$row = $recset->fetch(PDO::FETCH_ASSOC);
			if( !$row || $row['uni_name'] != $uni_name) {
				$errdata .= ($errdata?'<br>':'').'- Неправильно указан идентификатор материала.';
			}
			$recset->closeCursor();
		}

// spr_amount
		if( !$spr_amount )
			$errdata .= ($errdata?'<br>':'').'- Не указано количество материала.';
		else {
			$spr_amount = str_replace(',', '.', $spr_amount);
			if( !is_numeric( $spr_amount ) || $spr_amount <= 0 )
				$errdata .= ($errdata?'<br>':'')."- Неправильно указано количество материала.";
		}
		
//				
// Если есть ошибки - можно уже закончить
//
		if( $errdata ) throw new Exception($errdata, 1);
		
// Может быть это спецификация на изделие, которое прошло выпуск
		$sqlreq =	'SELECT prod_finish FROM products WHERE prod_id = '.$db->quote($prod_id);
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( !is_null($row['prod_finish']) ) {
			throw new Exception('В спецификации изделия, прошедшего выпуск никакие изменения не допускаются', 1);
        }    
		$recset->closeCursor();

// Может быть материал уже существует
		$sqlreq =	'SELECT count(*) AS count FROM specreal WHERE spr_uni_id = '.$db->quote($spr_uni_id).
					' AND spr_prod_id = '.$db->quote($prod_id);
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( $row['count'] != 0 ) {
			$errcode = -1;
			$errmess = "Предупреждение!!!. В спецификации уже присутствует запись с шифром '${uni_name}'";
        }    
		$recset->closeCursor();

// Пишем данные
        $sqlreq =   'INSERT INTO specreal ( spr_prod_id, spr_uni_id, spr_amount, spr_user_id ) '.
                    'VALUES ('. $db->quote($prod_id).", ".$db->quote($spr_uni_id).", ".
								$db->quote($spr_amount).", ".$db->quote($spr_user_id). ")";
												
		$db->exec($sqlreq);
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

  
  
  public function upduni() {

    $spr_id		= isset($_POST['id']) ? trim(strip_tags($_POST['id'])) : '';
    $prod_id	= isset($_POST['prod_id']) ? trim(strip_tags($_POST['prod_id'])) : '';
    $spr_uni_id	= isset($_POST['spr_uni_id']) ? trim(strip_tags($_POST['spr_uni_id'])) : '';
    $uni_name	= isset($_POST['uni_name']) ? trim(strip_tags($_POST['uni_name'])) : '';
    $spr_amount	= isset($_POST['spr_amount']) ? trim(strip_tags($_POST['spr_amount'])) : '';
	
	$errcode = 0;
	$errmess = '';
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$spr_user_id = $_SESSION['user_id'];
		
// Идентификатор изделия
		if( !$prod_id || !ctype_digit($prod_id) || !$spr_id || !ctype_digit($spr_id) )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
			
//Есть ли разрешение на вставку записей
		$rwd = $_SESSION['priv']->specreal->rwd;
		if( ($rwd & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
		$errdata = "";
		
// Открываем базу данных
		$db = App::me()->dbOpen();	

// uni_name			
		if( !$uni_name ) $errdata .= ($errdata?'<br>':'').'- Не указан шифр материала.';
		elseif( !$spr_uni_id || !ctype_digit( $spr_uni_id ) ) 
			$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор материала.';
		else {
// Считываем данные из справочника
			$recset = $db->query('SELECT uni_name FROM units WHERE uni_id = '.$db->quote($spr_uni_id));                  
			$row = $recset->fetch(PDO::FETCH_ASSOC);
			if( !$row || $row['uni_name'] != $uni_name) {
				$errdata .= ($errdata?'<br>':'').'- Неправильно указан идентификатор материала.';
			}
			$recset->closeCursor();
		}

// spr_amount
		if( !$spr_amount )
			$errdata .= ($errdata?'<br>':'').'- Не указано количество материала.';
		else {
			$spr_amount = str_replace(',', '.', $spr_amount);
			if( !is_numeric( $spr_amount ) || $spr_amount <= 0 )
				$errdata .= ($errdata?'<br>':'')."- Неправильно указано количество материала.";
		}
		
//				
// Если есть ошибки - можно уже закончить
//
		if( $errdata ) throw new Exception($errdata, 1);
		
// Есть ли разрешение на апдейт этой записи
		if( $rwd & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd & PERM_BITS::WRITE_GRP ) 
			$where = 'spr_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::WRITE_OWN ) $where = 'spr_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND spr_id = '.$db->quote($spr_id);
		
		$sqlreq = 'SELECT COUNT(*) AS count FROM specreal WHERE '.$where.' LIMIT 1';
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( !$row['count'] ) {
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
        }    
		$recset->closeCursor();
		
// Может быть это спецификация на изделие, которое прошло выпуск
		$sqlreq =	'SELECT prod_finish FROM products WHERE prod_id = '.$db->quote($prod_id);
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( !is_null($row['prod_finish']) ) {
			throw new Exception('В спецификации изделия, прошедшего выпуск никакие изменения не допускаются', 1);
        }    
		$recset->closeCursor();

// Может быть материал уже выписан со склада
		$sqlreq =	'SELECT spr_sto_id FROM specreal WHERE spr_id = '.$db->quote($spr_id);
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( $row['spr_sto_id'] ) {
			throw new Exception('Изменение этой позиции недопустимо, поскольку она содержит выписку по складу.', 1);
        }    
		$recset->closeCursor();

// Может быть материал уже существует
		$sqlreq =	'SELECT count(*) AS count FROM specreal WHERE spr_uni_id = '.$db->quote($spr_uni_id).
					' AND spr_id != '.$db->quote($spr_id).' AND spr_prod_id = '.$db->quote($prod_id);
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( $row['count'] != 0 ) {
			$errcode = -1;
			$errmess = "Предупреждение!!!. В спецификации уже присутствует запись с шифром '${uni_name}'";
        }    
		$recset->closeCursor();

// Пишем данные
        $sqlreq =   'UPDATE specreal SET '.
					'spr_prod_id = '.$db->quote($prod_id).", ".
					'spr_uni_id = '.$db->quote($spr_uni_id).", ".
					'spr_amount = '.$db->quote($spr_amount)." ".
					'WHERE spr_id = '.$db->quote($spr_id);
					
		$db->exec($sqlreq);
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

  
  public function deluni() {
    $errmess = "";
	$errcode = 0;
		
    $prod_id	= isset($_POST['prod_id']) ? trim(strip_tags($_POST['prod_id'])) : '';
    $spr_ids = (isset($_REQUEST['id']) && $_REQUEST['id'] != '') ?
										explode(",", $_REQUEST["id"], 100) : array();

    try
    {
// Только зарегистрированные пользователи могут удалять данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

// Есть ли разрешение на удаление хоть какой либо записи
		$rwd = $_SESSION['priv']->specreal->rwd;
		if( ($rwd & PERM_BITS::DELETE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);
		
// Идентификатор изделия и удаляемые материалы
        if ( !$prod_id || !ctype_digit($prod_id) || !Tools::is_numeric_array( $spr_ids ))
					throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
        
		$spr_id_list = implode(',', $spr_ids);

// Открываем базу данных
		$db = App::me()->dbOpen();	

// Есть ли разрешение на удаление этих записей
		if( $rwd & PERM_BITS::DELETE_OTH ) $where = '1 = 1';
		elseif( $rwd & PERM_BITS::DELETE_GRP ) 
			$where = 'spr_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::DELETE_OWN ) $where = 'spr_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND spr_id IN ('.$spr_id_list.')';
		
		$sqlreq = 'SELECT COUNT(*) AS count FROM specreal WHERE '.$where;
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( $row['count'] != count($spr_ids) ) {
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
        }    
		$recset->closeCursor();

// Может быть это спецификация на изделие, которое прошло выпуск
		$sqlreq =	'SELECT prod_finish FROM products WHERE prod_id = '.$db->quote($prod_id);
		$recset = $db->query( $sqlreq );		
		$row = $recset->fetch(PDO::FETCH_ASSOC);
		if( !is_null($row['prod_finish']) ) {
			throw new Exception('В спецификации изделия, прошедшего выпуск никакие изменения не допускаются', 1);
        }    
		$recset->closeCursor();

// Нет ли материалов, которые нужно вернуть на склад
		$recset = $db->query("SELECT spr_id FROM specreal WHERE spr_id IN ( ${spr_id_list} ) AND spr_sto_id > 0 LIMIT 1");
		if( $recset->fetch(PDO::FETCH_ASSOC) )
			throw new Exception('Сначала верните на склад материалы по которым есть выписка.', 1);
		
// Удаляем записи из справочника
        $sqlreq = "DELETE FROM specreal WHERE spr_id IN (".$spr_id_list.")";
        $db->exec($sqlreq);
	}
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }
  
  
// Получить состояние спецификации
  public function specstate() {
    $errmess = "";
	$errcode = 0;
	$specstate = 0x4;	// Запрещенное состояние
    try
    {
// Только зарегистрированные пользователи
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

// Идентификатор изделия
		$prod_id = (isset($_REQUEST['prod_id']) && ctype_digit($_REQUEST['prod_id'])) ? $_REQUEST['prod_id'] : '';
		if( !$prod_id )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);

// Открываем базу данных
		$db = App::me()->dbOpen();	
		$specstate = $this->getprodstatus( $db, $prod_id );
		if( $specstate === false )
			throw new Exception("Изделие отсутствует.", 1);
	}
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess,'data'=>$specstate);
  }
  
  private function getprodstatus( $db, $prod_id ) {
	$sql =	"SELECT ".
			"IF(( SELECT 1 FROM specreal WHERE spr_sto_id > 0 AND spr_prod_id = prod_id limit 1 ),2,0)+".
			"IF(( SELECT 1 FROM specreal WHERE spr_sto_id = 0 AND spr_prod_id = prod_id limit 1 ),0,4)+".
			"IF( prod_finish IS NULL,0,8) AS state FROM products WHERE prod_id = ${prod_id}";
	$rs = $db->query( $sql );
	$row = $rs->fetch(PDO::FETCH_ASSOC);
	
	if( !$row ) $status = false;
	else $status = isset( $this->legal_state[$row['state']] ) ? $row['state'] : "0";
	$rs->closeCursor();
	return $status;
  }
/*
    private function getspecstate( $db, $prod_id ) {
	$sql =	"( SELECT 1 AS sort_col, COUNT(*) AS num FROM specreal WHERE spr_prod_id = ${prod_id} AND spr_sto_id = 0 LIMIT 1 )".
			"  UNION ALL ".
			"( SELECT 2 AS sort_col, COUNT(*) AS num FROM specreal WHERE spr_prod_id = ${prod_id} AND spr_sto_id > 0 LIMIT 1 )".
			"  ORDER BY sort_col";
	$recset = $db->query( $sql );
	$row = $recset->fetch(PDO::FETCH_ASSOC);
	$stoNo = $row['num'];
	$row = $recset->fetch(PDO::FETCH_ASSOC);
	$stoYes = $row['num'];
	$recset->closeCursor();
	
	$specstate = 0;
	if( $stoYes ) $specstate |= 0x2;
	if( !$stoNo ) $specstate |= 0x4;

	return $specstate;
  }

select prod_id,
  IF(( select 1 from specreal where spr_sto_id > 0 and spr_prod_id = prod_id limit 1 ),2,0)+
  IF(( select 1 from specreal where spr_sto_id = 0 and spr_prod_id = prod_id limit 1 ),0,4) as status
from products where prod_id = 9
*/  
} // End od Products class
?>